/*
 *  Copyright (c) 2009 Alessandro Pilotti
 *
 *  Permission is hereby granted, free of charge, to any person
 *  obtaining a copy of this software and associated documentation
 *  files (the "Software"), to deal in the Software without
 *  restriction, including without limitation the rights to use,
 *  copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following
 *  conditions:
 *  
 *  The above copyright notice and this permission notice shall be
 *  included in all copies or substantial portions of the Software.
 *  
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *  OTHER DEALINGS IN THE SOFTWARE.
 */

#include "stdafx.h"

#include <initguid.h>
#include <vcclr.h>
#include <MAPIUTIL.H>

#include "OutlookAccountManager.h"
#include "AccountHelper.h"

#define MAPI_NO_COINIT 8

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Collections::Generic;
using namespace AlexPilotti::OutlookAccountManager;

void AccountManager::MAPIInitialize()
{
	// This is needed in cases when CoInitialize has already been called with a different threading model
	MAPIINIT_0 MAPIInit;
	MAPIInit.ulFlags = MAPI_NO_COINIT;
	MAPIInit.ulVersion = MAPI_INIT_VERSION;

	Marshal::ThrowExceptionForHR(::MAPIInitialize(&MAPIInit));
}

void AccountManager::MAPIUninitialize()
{
	::MAPIUninitialize();
}

IList<ProfileData^>^ AccountManager::GetProfiles()
{
	IList<ProfileData^>^ profileDataList = gcnew List<ProfileData^>();

	LPMAPITABLE pTable = NULL;
	LPPROFADMIN pProfAdmin = NULL;
	LPSRowSet pRows = NULL;
	ULONG ulCount = 0;

	try
	{
		Marshal::ThrowExceptionForHR(::MAPIAdminProfiles(0, &pProfAdmin));
		Marshal::ThrowExceptionForHR(pProfAdmin->GetProfileTable(0, &pTable));
		Marshal::ThrowExceptionForHR(pTable->GetRowCount(0,&ulCount));

		if(ulCount > 0)
		{			
			Marshal::ThrowExceptionForHR(::HrQueryAllRows(pTable, NULL, NULL, NULL, 0, &pRows));

			for(ULONG i = 0; i < pRows->cRows; i++)
			{
				ProfileData^ profileData = gcnew ProfileData();
				profileDataList->Add(profileData);

				for(ULONG j = 0; j < pRows->aRow[i].cValues; j++)
					switch(pRows->aRow[i].lpProps[j].ulPropTag)
					{
					case PR_DISPLAY_NAME_A:
						profileData->ProfileName = gcnew String(pRows->aRow[i].lpProps[j].Value.lpszA);
						break;
					case PR_DEFAULT_PROFILE:
						profileData->IsDefault = pRows->aRow[i].lpProps[j].Value.l != 0;
						break;
					}
			}
		}
	}
	finally
	{
		if(pRows)
			::FreeProws(pRows);

		if(pTable)
			pTable->Release();

		if(pProfAdmin)
			pProfAdmin->Release();
	}

	return profileDataList;
}

void AccountManager::InitAccountManager(String^ mapiProfileName, LPMAPISESSION& lpSession, LPOLKACCOUNTMANAGER& lpAcctMgr, CAccountHelper*& lpMyAcctHelper, LPOLKACCOUNTHELPER& lpAcctHelper)
{
	pin_ptr<const wchar_t> mapiProfileNamePtr = PtrToStringChars(mapiProfileName);

	Marshal::ThrowExceptionForHR(::MAPILogonEx(0, (LPTSTR)mapiProfileNamePtr, NULL, 
								 fMapiUnicode | MAPI_EXTENDED | MAPI_EXPLICIT_PROFILE | MAPI_NEW_SESSION | MAPI_NO_MAIL, 
								 &lpSession));

	Marshal::ThrowExceptionForHR(::CoCreateInstance(CLSID_OlkAccountManager, NULL, CLSCTX_INPROC_SERVER, IID_IOlkAccountManager, (LPVOID*)&lpAcctMgr));

	lpMyAcctHelper = new CAccountHelper(mapiProfileNamePtr, lpSession);
	if(!lpMyAcctHelper)
		throw gcnew OutOfMemoryException();

	Marshal::ThrowExceptionForHR(lpMyAcctHelper->QueryInterface(IID_IOlkAccountHelper, (LPVOID*)&lpAcctHelper));
	Marshal::ThrowExceptionForHR(lpAcctMgr->Init(lpAcctHelper, ACCT_INIT_NOSYNCH_MAPI_ACCTS));									
}


AccountData^ AccountManager::GetAccount(String^ mapiProfileName, unsigned accountId)
{
	AccountData^ accountData = nullptr;

	LPMAPISESSION lpSession;
	LPOLKACCOUNTMANAGER lpAcctMgr = NULL;
	CAccountHelper* lpMyAcctHelper = NULL;
	LPOLKACCOUNTHELPER lpAcctHelper = NULL;
	IOlkAccount* lpAccount = NULL;

	try
	{
		InitAccountManager(mapiProfileName, lpSession, lpAcctMgr, lpMyAcctHelper, lpAcctHelper);

		ACCT_VARIANT pProp = {0};	
		pProp.dwType = PT_LONG;
		pProp.Val.dw = accountId;	

		Marshal::ThrowExceptionForHR(lpAcctMgr->FindAccount(PROP_ACCT_ID, &pProp, &lpAccount));

		accountData = GetAccountData(lpAccount);

		Marshal::ThrowExceptionForHR(lpSession->Logoff(0, 0, 0));
	}
	finally
	{
		if(lpAccount)
			lpAccount->Release();

		if(lpAcctHelper)
			lpAcctHelper->Release();

		if(lpMyAcctHelper)
			lpMyAcctHelper->Release();

		if(lpAcctMgr)
			lpAcctMgr->Release();		

		if(lpSession)
			lpSession->Release();
	}	

	return accountData;
}


IList<AccountData^>^ AccountManager::GetAccounts(String^ mapiProfileName)
{			
	if(mapiProfileName == nullptr)
		throw gcnew Exception("MAPI profile name not provided");

	IList<AccountData^>^ accountDataList = gcnew List<AccountData^>();

	LPMAPISESSION lpSession;
	LPOLKACCOUNTMANAGER lpAcctMgr = NULL;
	CAccountHelper* lpMyAcctHelper = NULL;
	LPOLKACCOUNTHELPER lpAcctHelper = NULL;
	LPOLKENUM lpAcctEnum = NULL;

	try
	{
		InitAccountManager(mapiProfileName, lpSession, lpAcctMgr, lpMyAcctHelper, lpAcctHelper);

		Marshal::ThrowExceptionForHR(lpAcctMgr->EnumerateAccounts(&CLSID_OlkMail, NULL, OLK_ACCOUNT_NO_FLAGS, &lpAcctEnum));


		DWORD cAccounts = 0;						
		Marshal::ThrowExceptionForHR(lpAcctEnum->GetCount(&cAccounts));									
		Marshal::ThrowExceptionForHR(lpAcctEnum->Reset());

		DWORD i = 0;
		for (i = 0; i < cAccounts; i++)
		{
			LPUNKNOWN lpUnk = NULL;
			LPOLKACCOUNT lpAccount = NULL;

			try
			{					
				Marshal::ThrowExceptionForHR(lpAcctEnum->GetNext(&lpUnk));												
				Marshal::ThrowExceptionForHR(lpUnk->QueryInterface(IID_IOlkAccount, (LPVOID*)&lpAccount));						

				accountDataList->Add(GetAccountData(lpAccount));
			}
			finally
			{
				if(lpAccount)
					lpAccount->Release();

				if(lpUnk)
					lpUnk->Release();
			}
		}

		Marshal::ThrowExceptionForHR(lpSession->Logoff(0, 0, 0));
	}
	finally
	{
		if(lpAcctEnum)
			lpAcctEnum->Release();

		if(lpAcctHelper)
			lpAcctHelper->Release();

		if(lpMyAcctHelper)
			lpMyAcctHelper->Release();

		if(lpAcctMgr)
			lpAcctMgr->Release();		

		if(lpSession)
			lpSession->Release();
	}

	return accountDataList;
}


AccountData^ AccountManager::GetAccountData(LPOLKACCOUNT lpAccount)
{
	AccountData^ accountData = gcnew AccountData();

	CLSID clsidType;
	DWORD cCategories;
	CLSID* prgclsidCategory;
	Marshal::ThrowExceptionForHR(lpAccount->GetAccountInfo(&clsidType, &cCategories, &prgclsidCategory));

	if(clsidType == CLSID_OlkPOP3Account)
		accountData->AccountType = AccountType::POP3;
	else if(clsidType == CLSID_OlkIMAP4Account)
		accountData->AccountType = AccountType::IMAP4;
	else if(clsidType == CLSID_OlkMAPIAccount)
		accountData->AccountType = AccountType::MAPI;
	else if(clsidType == CLSID_OlkHotmailAccount)
		accountData->AccountType = AccountType::Hotmail;
	else if(clsidType == CLSID_OlkLDAPAccount)
		accountData->AccountType = AccountType::LDAP;
	else
		throw gcnew Exception("Unsupported account CLSID");

	for(unsigned i = 0; i < cCategories; i++)
	{
		AccountCategory category;

		if(prgclsidCategory[i] == CLSID_OlkMail)
			category = AccountCategory::Mail;
		else if(prgclsidCategory[i] == CLSID_OlkAddressBook)
			category = AccountCategory::AddressBook;
		else if(prgclsidCategory[i] == CLSID_OlkStore)
			category = AccountCategory::Store;
		else
			throw gcnew Exception("Unsupported account category CLSID");

		accountData->Categories->Add(category);
	}

	accountData->AccountID = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_ID).Value;
	accountData->AccountName = GetPropertyValueString(lpAccount, PROP_ACCT_NAME);
	accountData->ProfileName = GetPropertyValueString(lpAccount, PROP_ACCT_PROFILE_NAME);

	Nullable<unsigned> uVal = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_IS_EXCH);
	if(uVal.HasValue)
		accountData->IsExchange = uVal.Value != 0;

	accountData->SendStamp = GetPropertyValueString(lpAccount, PROP_ACCT_SEND_STAMP);
	accountData->AccountStamp = GetPropertyValueString(lpAccount, PROP_ACCT_STAMP);

	uVal = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_POP3_REMEMBER_PASSWORD);
	if(uVal.HasValue)
		accountData->IncomingRememberPassword = uVal.Value != 0;

	accountData->UserDisplayName = GetPropertyValueString(lpAccount, PROP_ACCT_USER_DISPLAY_NAME);
	accountData->AccountTypeDescription = GetPropertyValueString(lpAccount, PROP_ACCT_TYPE);
	accountData->EmailID = GetPropertyValueString(lpAccount, PROP_ACCT_ADDRESS);

	accountData->IncomingServer = GetPropertyValueString(lpAccount, PROP_ACCT_POP3_SERVER);
	accountData->IncomingLoginName = GetPropertyValueString(lpAccount, PROP_ACCT_POP3_NAME);

	uVal = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_POP3_PORT);
	if(uVal.HasValue)
		accountData->IncomingPort = (unsigned short)uVal.Value;
	
	uVal = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_POP3_USE_SSL);
	if(uVal.HasValue)
		accountData->IncomingSSL = uVal.Value != 0;

	uVal = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_POP3_USE_SPA);
	if(uVal.HasValue)
		accountData->IncomingSPA = uVal.Value != 0;

	accountData->OutgoingServer = GetPropertyValueString(lpAccount, PROP_ACCT_SMTP_SERVER);
	accountData->OutgoingLoginName = GetPropertyValueString(lpAccount, PROP_ACCT_SMTP_NAME);
	accountData->OutgoingServerAuthenticationType = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_SMTP_LOGON_KIND);
	
	uVal = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_SMTP_PORT);
	if(uVal.HasValue)	
		accountData->OutgoingPort = (unsigned short)uVal.Value;

	uVal = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_SMTP_USE_SSL);
	if(uVal.HasValue)	
		accountData->OutgoingSSL = uVal.Value != 0;

	uVal = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_SMTP_USE_AUTH);
	if(uVal.HasValue)	
		accountData->OutgoingUseAuthentication = uVal.Value != 0;

	uVal = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_USE_SPA);
	if(uVal.HasValue)	
		accountData->OutgoingSPA = uVal.Value != 0;

	accountData->TimeOut = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_TIMEOUT);
	accountData->AccountFlags = GetPropertyValueUnsigned(lpAccount, PROP_ACCT_FLAGS);

	accountData->ExchangeEmailID = GetPropertyValueString(lpAccount, PROP_EXCHANGE_EMAILID);
	accountData->ExchangeEmailID2 = GetPropertyValueString(lpAccount, PROP_EXCHANGE_EMAILID2);
	accountData->ExchangeServer = GetPropertyValueString(lpAccount, PR_INTERNET_CONTENT_ID);

	return accountData;
}

String^ AccountManager::GetPropertyValueString(LPOLKACCOUNT lpAccount, DWORD dwProp)
{	
	String^ value = nullptr;
	ACCT_VARIANT pProp = {0};

	if(SUCCEEDED(lpAccount->GetProp(dwProp, &pProp)))
	{
		value = gcnew String(pProp.Val.pwsz);
		lpAccount->FreeMemory((LPBYTE)pProp.Val.pwsz);
	}

	return value;
}

Nullable<unsigned> AccountManager::GetPropertyValueUnsigned(LPOLKACCOUNT lpAccount, DWORD dwProp)
{
	Nullable<unsigned> value;
	ACCT_VARIANT pProp = {0};

	if(SUCCEEDED(lpAccount->GetProp(dwProp, &pProp)))
		value = pProp.Val.dw;

	return value;
}

void AccountManager::SaveAccountData(AccountData^ accountData)
{
	LPMAPISESSION lpSession;
	LPOLKACCOUNTMANAGER lpAcctMgr = NULL;
	CAccountHelper* lpMyAcctHelper = NULL;
	LPOLKACCOUNTHELPER lpAcctHelper = NULL;
	IOlkAccount* lpAccount = NULL;

	try
	{
		InitAccountManager(accountData->ProfileName, lpSession, lpAcctMgr, lpMyAcctHelper, lpAcctHelper);

		ACCT_VARIANT pProp = {0};	
		pProp.dwType = PT_LONG;
		pProp.Val.dw = accountData->AccountID;	

		Marshal::ThrowExceptionForHR(lpAcctMgr->FindAccount(PROP_ACCT_ID, &pProp, &lpAccount));

		SetAccountData(accountData, lpAccount);

		Marshal::ThrowExceptionForHR(lpSession->Logoff(0, 0, 0));
	}
	finally
	{
		if(lpAccount)
			lpAccount->Release();

		if(lpAcctHelper)
			lpAcctHelper->Release();

		if(lpMyAcctHelper)
			lpMyAcctHelper->Release();

		if(lpAcctMgr)
			lpAcctMgr->Release();		

		if(lpSession)
			lpSession->Release();
	}	
}


void AccountManager::SetAccountData(AccountData^ accountData, LPOLKACCOUNT lpAccount)
{
	SetProp(lpAccount, PROP_ACCT_NAME, accountData->AccountName);	
	SetProp(lpAccount, PROP_ACCT_USER_DISPLAY_NAME, accountData->UserDisplayName);
	//SetProp(lpAccount, PROP_ACCT_TYPE, accountData->AccountTypeDescription);
	SetProp(lpAccount, PROP_ACCT_ADDRESS, accountData->EmailID);
	SetProp(lpAccount, PROP_ACCT_POP3_SERVER, accountData->IncomingServer);
	SetProp(lpAccount, PROP_ACCT_POP3_NAME, accountData->IncomingLoginName);
	SetProp(lpAccount, PROP_ACCT_POP3_PORT, accountData->IncomingPort);
	SetProp(lpAccount, PROP_ACCT_POP3_USE_SSL, accountData->IncomingSSL);
	SetProp(lpAccount, PROP_ACCT_POP3_USE_SPA, accountData->IncomingSPA);
	SetProp(lpAccount, PROP_ACCT_POP3_REMEMBER_PASSWORD, accountData->IncomingRememberPassword);
	SetProp(lpAccount, PROP_ACCT_SMTP_SERVER, accountData->OutgoingServer);
	SetProp(lpAccount, PROP_ACCT_SMTP_NAME, accountData->OutgoingLoginName);
	SetProp(lpAccount, PROP_ACCT_SMTP_LOGON_KIND, accountData->OutgoingServerAuthenticationType);
	SetProp(lpAccount, PROP_ACCT_SMTP_PORT, accountData->OutgoingPort);
	SetProp(lpAccount, PROP_ACCT_SMTP_USE_SSL, accountData->OutgoingSSL);
	SetProp(lpAccount, PROP_ACCT_SMTP_USE_AUTH, accountData->OutgoingUseAuthentication);
	SetProp(lpAccount, PROP_ACCT_USE_SPA, accountData->OutgoingSPA);
	SetProp(lpAccount, PROP_ACCT_TIMEOUT, accountData->TimeOut);	
	SetProp(lpAccount, PROP_EXCHANGE_EMAILID, accountData->ExchangeEmailID);	
	SetProp(lpAccount, PROP_EXCHANGE_EMAILID2, accountData->ExchangeEmailID2);
	SetProp(lpAccount, PR_INTERNET_CONTENT_ID, accountData->ExchangeServer);		

	Marshal::ThrowExceptionForHR(lpAccount->SaveChanges(OLK_ACCOUNT_NO_FLAGS));
}

void AccountManager::SetProp(LPOLKACCOUNT lpAccount, DWORD dwProp, String^ value)
{
	if(value)
	{
		ACCT_VARIANT pProp = {0};	
		pProp.dwType = PT_UNICODE;
		pin_ptr<const wchar_t> strPtr = PtrToStringChars(value);
		pProp.Val.pwsz = (LPWSTR)strPtr;	

		Marshal::ThrowExceptionForHR(lpAccount->SetProp(dwProp, &pProp));
	}
}

void AccountManager::SetProp(LPOLKACCOUNT lpAccount, DWORD dwProp, Nullable<unsigned> value)
{
	if(value.HasValue)
	{
		ACCT_VARIANT pProp = {0};	
		pProp.dwType = PT_LONG;
		pProp.Val.dw = value.Value;	

		Marshal::ThrowExceptionForHR(lpAccount->SetProp(dwProp, &pProp));
	}
}

void AccountManager::SetProp(LPOLKACCOUNT lpAccount, DWORD dwProp, Nullable<unsigned short> value)
{
	if(value.HasValue)
		SetProp(lpAccount, dwProp, Nullable<unsigned>(value));
}

void AccountManager::SetProp(LPOLKACCOUNT lpAccount, DWORD dwProp, Nullable<bool> value)
{
	if(value.HasValue)
	{
		ACCT_VARIANT pProp = {0};	
		pProp.dwType = 3;
		pProp.Val.dw = value.Value ? TRUE : FALSE;	

		Marshal::ThrowExceptionForHR(lpAccount->SetProp(dwProp, &pProp));
	}
}
